﻿package org.cove.ape {
	import org.cove.ape.CircleParticle;
	import org.cove.ape.Quaternion;
	import de.gameduell.framework.debug.Debug;	
	import flash.errors.IllegalOperationError;	

	public class SphereParticle extends CircleParticle implements Clonable{
		
		public var maxSpeed:Number = 10;
		public var maxSpin:Number  = 5;
		 
		// -/+ changes tangential direction of shift (- = reverse impulse)
	   	public var spinShiftFactor:Number = -1;
	   	public var spinZToTranslationalDirectionChangeFactor:Number = -1;
	   	public var spinImpulseOnContactFactor:Number = +0.005;
		
		// angle shift by roll or spin (spin should be higher than roll!)
	   	public var spinAngleShiftFactor:Number = 0.18;
		public var rollAngleShiftFactor:Number = 0.06;
	
	   	// spin impulse factors
	   	public var impulseSign:Number = 1;
	   	public var impulseWeightSelf:Number = 0.6;
	   	public var impulseWeightOther:Number = 0.4; // mixed if more than one

		// angle
		public var rotation:Quaternion = new Quaternion(0, 0, 0, 1);

		// additive/subtractive angular speed movement (rotation -> translation)
		public var spin:Vector3f = new Vector3f();

		// the drift or roll of z-rotation is very low
		public var rotationToTranslationConversionByRollingEffetXY:Number = 0.005;

		// exp friction on low speed
		public var translationalFrictionExpXY:Number = 0.01;  
		public var rotationalFrictionExpXY:Number = 0.05; 
		public var rotationalFrictionExpZ:Number = 0.01;  

		// exp friction on high speed
		public var translationalFrictionLinXY:Number = 0.001;  
		public var rotationalFrictionLinXY:Number = 0.0005; 
		public var rotationalFrictionLinZ:Number = 0.001;  
		
		// exp friction on high speed
		public var translationalFrictionConstXY:Number = 0.001;  
		public var rotationalFrictionConstXY:Number = 0.005; 
		public var rotationalFrictionConstZ:Number = 0.001;  

		// curent axis (temporary)
		private var currentAxisRotation:Quaternion = new Quaternion();
		//private var lasteffet:Vector3f = new Vector3f();
		private var lastSamp:Vector = new Vector();
		private var lastCenter:Vector= new Vector();
		private var lastRotation:Quaternion = new Quaternion();

		// slide after contact or hit
		private var majorFramesAfterContact:int = 0;
		private var fadingMajorFramesAfterContact:int = 10; // when too high, there is no drift effect after contact

		// CONTROLLED BY ENGINE
		public var collidedParticles:Array;
		public var onMajorFrame:Boolean = true;
		
		public function SphereParticle(apEngine:APEngine,x:Number = 0, y:Number = 0, radius:Number = 0, fixed:Boolean = false, mass:Number = 1, elasticity:Number = 0.3, friction:Number = 0) {
			super(apEngine,x, y, radius, fixed, mass, elasticity, friction);
			collidedParticles = new Array();
		}
		
		public function copySphere(sphere:SphereParticle):void{
			copyCircle(sphere);

			rotation = sphere.rotation.clone();
			spin = sphere.spin.clone();
			currentAxisRotation = sphere.currentAxisRotation.clone();
			
			//translationToRotationConversionOnRoll = sphere.translationToRotationConversionOnRoll;
//			rotationToTranslationConversionOnRoll = sphere.rotationToTranslationConversionOnRoll;
			rotationToTranslationConversionByRollingEffetXY = sphere.rotationToTranslationConversionByRollingEffetXY;
			
			rotationalFrictionExpXY = sphere.rotationalFrictionExpXY;
			rotationalFrictionLinXY = sphere.rotationalFrictionLinXY;
			rotationalFrictionConstXY = sphere.rotationalFrictionConstXY;
			
			rotationalFrictionExpZ = sphere.rotationalFrictionExpZ;
			rotationalFrictionLinZ = sphere.rotationalFrictionLinZ;
			rotationalFrictionConstZ = sphere.rotationalFrictionConstZ;

			translationalFrictionExpXY = sphere.translationalFrictionExpXY;
			translationalFrictionLinXY = sphere.translationalFrictionLinXY;
			translationalFrictionConstXY = sphere.translationalFrictionConstXY;
			
			rollAngleShiftFactor = sphere.rollAngleShiftFactor;
			spinAngleShiftFactor = sphere.spinAngleShiftFactor;

			majorFramesAfterContact = sphere.majorFramesAfterContact;
			fadingMajorFramesAfterContact = sphere.fadingMajorFramesAfterContact;
		
			impulseSign = sphere.impulseSign;
			impulseWeightSelf = sphere.impulseWeightSelf;
			impulseWeightOther = sphere.impulseWeightOther;
		
			maxSpeed = sphere.maxSpeed;
			maxSpin = sphere.maxSpin;
		
			spinShiftFactor = sphere.spinShiftFactor;
			spinZToTranslationalDirectionChangeFactor = sphere.spinZToTranslationalDirectionChangeFactor;
			spinImpulseOnContactFactor = sphere.spinImpulseOnContactFactor;
				
		}
		
		public function updateAfterContactOrResting():void {
			majorFramesAfterContact = 0;
		}

		public function clone(aEngine:APEngine):Clonable {
			var clone:SphereParticle = new SphereParticle(aEngine, curr.x, curr.y, _radius);
			clone.copySphere(this);
			return clone;	
		}

		public static function getAxisRotationAsQuaternion( q:Quaternion,axis:Vector3f,angle:Number):void {
			var d:Number = axis.length();

			if(d == 0) {
				throw new IllegalOperationError("d must not be zero!");
			}
			var s:Number = Math.sin(angle * 0.5) / d;
			q.set(axis.x * s, axis.y * s, axis.z * s, Math.cos(angle * 0.5));
		}

		/**
		 * STEP HANDLING (ROLL INTEGRATION + FRICTION INTEGRATION)
		 */			
		override public function update(dt:Number, isMajorFrame:Boolean):void {
			super.update(dt, isMajorFrame);
			
			// keep sync
			this.onMajorFrame = isMajorFrame;

			// reset collisions on major frames
			if (onMajorFrame) {
				
				// HANDLE ONLY BY ENGINE
				// collidedParticles.length = 0;
				
				// increment on every major frame
				if (majorFramesAfterContact < fadingMajorFramesAfterContact) {
					majorFramesAfterContact++;
				}
			}
			
			if(fixed){
				return;
			}
			
			// slide filter after contact
			var slideFilterAfterContact:Number = Math.min(1, Number(majorFramesAfterContact) / Number(fadingMajorFramesAfterContact));
			
			// remember each step (major+minor)			
			//lasteffet = spin;
			lastSamp = samp;
			lastCenter = center;
			lastRotation = rotation.clone();

			///////////////////////////////////////
			// stop when slower then tresholds ...

			// get old velocity
			var v2:Vector = velocity;
			if (v2.magnitude() < TRANSLATIONAL_STOP_TRESHOLD && spin.length() < ROTATIONAL_STOP_TRESHOLD) {
				spin.set(0, 0, 0);
				v2.setTo(0, 0);
				velocity = v2;
				return;
			}

			// in sub frames go only straigth lines
			// => primary:   avoid incorrect exponential friction within sub frames
			// => secondary: more performance
			if (isMajorFrame) {

				// CONVERT V(X,Y) -> ALPHA(Y,X)
				convertTranslationToAngleShift(dt);

				// CONVERT ROLL effet(X,Y) -> V(X,Y)
				convertEffetToTranslationalRollXY(dt, slideFilterAfterContact);
				
				// CONVERT effet(X,Y,Z) -> ANGLE(X,Y,Z)
				convertEffetToAngleShift(dt);

				// ROLL/GLUE FRICTION ON V(X,Y)
				reduceTranslation(dt);

				// ROLL/GLUE FRICTION ON effet(X,Y) 
				reduceEffetRollXY(dt); 
				// EQUAL COEEFS FROM reduceTranslation V(X,Y) !!!

				// SLIDE FRICTION ON effet(Z) 
				reduceEffetSlideZ(dt); // INDEPENDENT FROM reduceTranslation V(X,Y)
			}
			
			// inform about changes ...
			//if (_apEngine.heuristicMovmementStateListener != null) {
			//	_apEngine.heuristicMovmementStateListener.onChange(this);
			//}
			 
		}
		
		override public function backStep():void{
			super.backStep();
			//spin = lasteffet.clone();
			samp = lastSamp.clone();
			_center = lastCenter.clone();
			lastRotation = rotation.clone();
			collidedParticles.length = 0;
		}

		
		/**
		 * ROLL FX
		 */
		public function convertTranslationToAngleShift( dt:Number):void {
			/////////////////////////////////
			// ROLL TRANSLATION -> ROTATION

			// TODO: only apply if effet is small: effet.length() < 0.1

			//var directionSpin:Number = Math.sqrt(spin.x * spin.x + spin.y * spin.y);
			
			// get old velocity
			var v2:Vector = velocity;
			if (v2.magnitude() > 0.00001 /* && directionSpin < 1*/ ) {
				
				// ratio spin vs. roll
				var directionSpinRatio:Number = Math.sqrt(spin.x * spin.x + spin.y * spin.y) / maxSpin;
				var appliedRollAngleShift:Number = 1 / Math.max(1, directionSpinRatio);
				
				// calc roll axis
				var rollAngle:Number = v2.magnitude() * rollAngleShiftFactor * appliedRollAngleShift;
				//var rollAxis:Vector3f = new Vector3f(-v2.y, v2.x, 0);
				//rollAxis = rotateZ90.multVector(rollAxis);
				var rollAxis:Vector = rotate(v2, rollAngle);
			
				// get axis rotation as quat
				getAxisRotationAsQuaternion(currentAxisRotation, new Vector3f(rollAxis.x, rollAxis.y, 0), rollAngle);
			
				// connect rotation in worldcoordinates
				rotation = currentAxisRotation.connect(rotation);
			}
		}
		
		
		private function convertEffetToAngleShift(dt:Number):void {	
			
			//var directionRollAngleRatio:Number = velocity.magnitude() / maxSpeed;
			//var appliedAngleShift:Number = 1 / Math.max(1, directionRollAngleRatio);
				
			// get effet z as rotatation and connect rotation in worldcoordinates
			getAxisRotationAsQuaternion(currentAxisRotation, Vector3f.UNIT_X, spin.y * spinAngleShiftFactor);
			rotation = currentAxisRotation.connect(rotation);

			// get effet y as rotatation and connect rotation in worldcoordinates
			getAxisRotationAsQuaternion(currentAxisRotation, Vector3f.UNIT_Y, -spin.x * spinAngleShiftFactor);
			rotation = currentAxisRotation.connect(rotation);

			// get effet z as rotatation and connect rotation in worldcoordinates
			getAxisRotationAsQuaternion(currentAxisRotation, Vector3f.UNIT_Z, spin.z * spinAngleShiftFactor);
			rotation = currentAxisRotation.connect(rotation);
		}

		private function convertEffetToTranslationalRollXY(dt:Number, slideFilterAfterContact:Number):void {
			
			// high speed => low translation shift
			// low speed  => high translation shift
			
			// 1 / v with v of [1 .. n]
			var frictionFactorBySpeed:Number = 1 / Math.max(1, velocity.magnitude());
			
			// limit spin
			var limitedSpin:Vector = new Vector(spin.x, spin.y);
			if (limitedSpin.magnitude() > 1) {
				limitedSpin = limitedSpin.normalize();
				limitedSpin.multEquals(1);
			}
			
			// limit drift by speed, bacause movement by drift should not be higher than speed
			var dx:Number = limitedSpin.y * rotationToTranslationConversionByRollingEffetXY * frictionFactorBySpeed * slideFilterAfterContact;
			var dy:Number = limitedSpin.x * rotationToTranslationConversionByRollingEffetXY * frictionFactorBySpeed * slideFilterAfterContact;
			var drift:Number = Math.sqrt(dx * dx + dy * dy);
			var speed:Number = velocity.magnitude();
			
			// limit the spin-drift vs. speed;
			// if speed == 0, let spin work 
			// => causes invert movement direction on ball translation stop
			if (drift > speed && speed > TRANSLATIONAL_STOP_TRESHOLD) {
				dx = (dx / drift) * speed;
				dy = (dy / drift) * speed;
			}
			
						
			// effetXY -> moveXY 
			curr.x += dx;
			curr.y -= dy;
		}

		/*
		private function convertEffetToTranslationalSlideZ(dt:Number):void {
			// INVERSE SLIDE FRICTION
		
			// effetZ -> rotateV
			var v2:Vector = velocity;
			var speed:Number = v2.magnitude();
			var inverseSpeed:Number = rotationToTranslationConversionBySlidingEffetZA / (rotationToTranslationConversionBySlidingEffetZB + rotationToTranslationConversionBySlidingEffetZC * speed);

			// rotational slide only on movement, otherwise just rotatate
			if (speed > 0.00001 && Math.abs(effet.z) > 0.000001) {

				// calc roll axis and angle around z
				var driftAngle:Number =  effet.z * inverseSpeed;
				velocity = rotate(v2, -driftAngle);
			}
		}
		*/


		private function reduceTranslation(dt:Number):void {
			///////////////////////////////////
			// ROLL FRICTION ON V (Haftreibung)
 
			// get old velocity
			var vXY:Vector = velocity;
			var absVXY:Number = vXY.magnitude();
 
			// apply friction
			var friction:Number = getFrictionForSpeed(absVXY, translationalFrictionExpXY, translationalFrictionLinXY, translationalFrictionConstXY);
			var frictionV:Number = friction;
			var dvFriction:Vector = vXY.mult(frictionV);
 
			if (dvFriction.magnitude() <= absVXY) {
				vXY.x -= dvFriction.x;
				vXY.y -= dvFriction.y;
			} else {
				vXY.x = 0;
				vXY.y = 0;
			}
           
			velocity = vXY;
		}

		/**
		 * rotate around local center
		 * @param direction
		 * @param angle
		 */
		public function rotate(direction:Vector, angle:Number):Vector {
			var sin:Number = Math.sin(angle);
			var cos:Number = Math.cos(angle);
			var temp:Vector = new Vector(direction.x, direction.x);
			temp.x = (cos * direction.x - sin * direction.y);
			temp.y = (sin * direction.x + cos * direction.y);
			return temp;
		} 

		private function reduceEffetRollXY(dt:Number):void {
			///////////////////////////////////
			// wx,wy ROLL-FRICTION (Haftreibung) := EQUAL COEEFS FROM reduceTranslation V(X,Y) !!!
			var wXY:Vector = new Vector(spin.x, spin.y);
         
			// apply friction
			var friction:Number = getFrictionForSpeed(wXY.magnitude(), rotationalFrictionExpXY, rotationalFrictionLinXY, rotationalFrictionConstXY);
			var frictionWXY:Number = friction ;
			var dwFriction:Vector = wXY.mult(frictionWXY);
 
			if (dwFriction.magnitude() <= wXY.magnitude()) {
				spin.x -= dwFriction.x;
				spin.y -= dwFriction.y;
			} else {
				spin.x = 0;
				spin.y = 0;
			}
		}

		private function reduceEffetSlideZ(dt:Number):void {
			
			///////////////////////////////////
			// wz SLIDE-FRICTION := INDEPENDENT FROM reduceTranslation V(X,Y)
			var wZ:Number = spin.z;
			var absWZ:Number = Math.abs(spin.z);
        
			// apply friction
			var friction:Number = getFrictionForSpeed(absWZ, rotationalFrictionExpZ, rotationalFrictionLinZ, rotationalFrictionConstZ);
			var frictionWZ:Number = friction * dt;
			var dwFriction:Number = wZ * frictionWZ;
 
			if (Math.abs(dwFriction) <= absWZ) {
				spin.z -= dwFriction;
			} else {
				spin.z = 0;
			}
			
		} 

		/**
		 * invalidate last collision point
		 */
		//public function resetLastCollisionPoint():void {
			// lastCollisionPoint = new Vector (-1000, -1000);
		//}
		
		/**
		 * COLLISION HANDLING (OMEGA REFLECTION + TANGENTIAL FX ON V + + TANGENTIAL FX ON W)
		 */		
		override public function resolveCollision( 
			mtd:Vector, 
			velocity:Vector, 
			normal:Vector,
			depth:Number,
			order:int,
			otherParticle:AbstractParticle, 
			vnBefore:Vector,
			vtBefore:Vector):void {
		
			super.resolveCollision(mtd, velocity, normal, depth, order, otherParticle, vnBefore, vtBefore);
			

			// set flag on collision
			collidedParticles.push(otherParticle);

//			Debug.trace("resolveCollision() mtd=" + mtd + 
//				" velocity=" + velocity + 
//				" normal=" + normal + 
//				" depth=" +depth + 
//				" order=" + order + 
//				" otherParticle=" + otherParticle +
//				" vnBefore=" + vnBefore + 
//				" vtBefore=" + vtBefore +
//				" collidedParticles=" + collidedParticles					
//			);
			
			// change direction by spin and damp spin
			if (!onMajorFrame) {
				
				// change direction by spin
				convertSpinZToTranslationalDirectionChangeXYOnContact(normal, velocity, vnBefore, vtBefore, (otherParticle is RectangleParticle));
				
				// DAMPING := impulseWeightSelf
				spin.x = impulseSign * impulseWeightSelf * spin.x;
				spin.y = impulseSign * impulseWeightSelf * spin.y;
				spin.z = impulseSign * impulseWeightSelf * spin.z;
				
			}

			// reset frames after contact
			majorFramesAfterContact = 0;
			
			/*
			// collision point
			//var dx = Math.abs(lastCollisionPoint.x - position.x);
			//var dy = Math.abs(lastCollisionPoint.y - position.y);
			// Debug.trace("collision dx=" + dx + " dy=" + dy);
			//var wasCollisionPointAlreadyProcessed:Boolean = dx < collisionTolerance && dy < collisionTolerance;
			//if (!wasCollisionPointAlreadyProcessed) {
				
				
				// change direction by spin (only on cushion, before damping!)
				if (otherParticle is RectangleParticle) {
					// change direction by spin
					convertSpinZToTranslationalDirectionChangeXYOnContact(normal, velocity, vnBefore, vtBefore);
				}
				
				// done by super.resolveCollision()
				// _collisionVelocity = this.velocity.clone();

				// DAMPING := impulseWeightSelf
				spin.x = impulseSign * impulseWeightSelf * spin.x;
				spin.y = impulseSign * impulseWeightSelf * spin.y;
				spin.z = impulseSign * impulseWeightSelf * spin.z;
			//}
			
			// always remember last collision point
			//lastCollisionPoint = position; 
			*/
			
			/*
			// spin-impulse-conversion ball:ball
	       	if (otherParticle is SphereParticle) {				
				
//	        	var otherSphereParticle:SphereParticle  = SphereParticle(otherParticle);
//	           
//	           	// calculate effee impulse distribution
//	           	var mixedX:Number = (spin.x + otherSphereParticle.spin.x) / 2;
//	           	var mixedY:Number = (spin.y + otherSphereParticle.spin.y) / 2;
//	           	var mixedZ:Number = (spin.z + otherSphereParticle.spin.z) / 2;
//	
//	           	// this: distribute mixed (primary keep own, and reverse other)
//	           	spin.z = (impulseSign * spin.x * impulseWeightSelf) - (impulseSign * mixedX * impulseWeightOther);
//	           	spin.z = (impulseSign * spin.y * impulseWeightSelf) - (impulseSign * mixedY * impulseWeightOther);
//	           	spin.z = (impulseSign * spin.z * impulseWeightSelf) - (impulseSign * mixedZ * impulseWeightOther);

	           	// DAMPING := impulseWeightSelf
				spin.x = impulseSign * impulseWeightSelf * spin.x;
				spin.y = impulseSign * impulseWeightSelf * spin.y;
				spin.z = impulseSign * impulseWeightSelf * spin.z;
	       	}
           	// spin-impulse-conversion ball:border
           	else {
	           	// DAMPING := impulseWeightSelf
				spin.x = impulseSign * impulseWeightSelf * spin.x;
				spin.y = impulseSign * impulseWeightSelf * spin.y;
				spin.z = impulseSign * impulseWeightSelf * spin.z;
           	}
			
           	
			// change direction by spin
			//convertSpinZToTranslationalDirectionChangeXYOnContact(normal, velocity, vnBefore, vtBefore);
           	
           	// inform about changes ...
			if (_apEngine.heuristicMovmementStateListener != null) {
				_apEngine.heuristicMovmementStateListener.onChange(this);
			}
			_collisionVelocity = this.velocity.clone();
			*/
		}

		private function convertSpinZToTranslationalDirectionChangeXYOnContact(normal:Vector, velocityBefore:Vector, vn:Vector, vt:Vector, isBorder:Boolean) : void {
			
			// only if effet exists
			if (Math.abs(spin.z) < ROTATIONAL_STOP_TRESHOLD) {
				return;
			}

			// normalized angle ratio normal vs. tangent
			//var speed:Number = velocityBefore.magnitude();
			//var vtn:Vector   = vt.mult(1  / (0.00001 + speed));
			//var vnn:Vector   = vn.mult(1  / (0.00001 + speed));
			//var angleRatio:Number = vtn.magnitude() / (0.00001 + vnn.magnitude());
			//var limitedAngleRatio:Number = 1 - (Math.min(angleRatio, 5) / 5);
			//var angleRatio:Number = (0.1 + vtn.magnitude()) / (0.1 + vnn.magnitude());
			//var limitedAngleRatio:Number = 1 - (Math.min(angleRatio, 6) / 6);
			
			/*
			// limit the angle
			//var limitedAngleInRadians = Math.min(angleInRadians, Math.PI / 6);
			// var inverseAngleRatio:Number = Math.min(1, vnn.magnitude() / (0.00001 + 2 * vtn.magnitude() * vtn.magnitude()));
			var atanAlpha:Number = Math.atan2(vnn.magnitude(), vtn.magnitude());
			var atanAlphaSquared = atanAlpha * atanAlpha;
			var limitedAngleInRadians:Number = 0;
			if (angleInRadians > 0) {
				limitedAngleInRadians = (0.2 * atanAlphaSquared) * Math.min(angleInRadians, + Math.PI / 3);
			}
			else {
				limitedAngleInRadians = (0.2 * atanAlphaSquared) * Math.max(angleInRadians, - Math.PI / 3);
			}
			*/
			
			// calculate limited angle ratio between normal and tangent
			var tFactor:Number = Math.min(20, Math.max(0.01, vt.magnitude()));
			var nFactor:Number = Math.min(20, Math.max(0.01, vn.magnitude()));
			var angleRatio:Number = Math.min(1, tFactor / nFactor);
			var limitedAngleRatio:Number = 1 - angleRatio;

			// effet {0 ... MAX_EFFET} {0 ... 1}
			var effetRatio:Number = Math.abs(spin.z) / maxSpin;
			var effetSign:Number  = spin.z / Math.abs(spin.z);
			var angleInRadians:Number 	  = Math.PI * effetSign * effetRatio * spinZToTranslationalDirectionChangeFactor * limitedAngleRatio;
			
			// limit result angle
			var limitedAngleInRadians:Number = 0;
			if (angleInRadians > 0) {
				limitedAngleInRadians = Math.min(angleInRadians, + Math.PI / 4);
			}
			else {
				limitedAngleInRadians = Math.max(angleInRadians, - Math.PI / 4);
			}
			
			// apply the fx
			var oldVelocity : Vector = velocity;
			// var newVelocity : Vector = oldVelocity.rotate(limitedAngleInRadians);
			var newVelocity : Vector = null;
			if (isBorder) {
				newVelocity = oldVelocity.rotate(limitedAngleInRadians);
			}
			else {
				newVelocity = oldVelocity.rotate(- 0.5 * limitedAngleInRadians);
			}
			velocity = newVelocity;	
			
			//Debug.trace("[contact] angleRatio="+angleRatio+" angleInRadians="+angleInRadians+" limitedAngle="+ limitedAngleInRadians +" oldVelocity=[" + oldVelocity + "] newVelocity=[" + newVelocity + "]");
		}

		// exponentional -> f(x) := K * 1 / Exp(x) => v(0) = 1, v(max) = 0.01 v(infinity) = 0; 
		// linear -> f(x) := K * 1 / Exp(x) => v(0) = 1, v(max) = 0.01 v(infinity) = 0; 
		public static function getFrictionForSpeed( v:Number,  expCoefficient:Number,  linCoefficient:Number,  contantCoefficient:Number):Number {
		
			// EXPONENTIAL
			var exponentialFriction:Number = expCoefficient / Math.exp(v);

			// linear for hight speed
			exponentialFriction += linCoefficient * v;		

			// constant
			exponentialFriction += contantCoefficient;
		
			return exponentialFriction;
		}
		
		public override function isStopped():Boolean {
			//return stopped;
			var noSpeed:Boolean = velocity.magnitude() < TRANSLATIONAL_STOP_TRESHOLD && spin.length() < ROTATIONAL_STOP_TRESHOLD;
			return  noSpeed || fixed;
		}

	}
}

		
